/* Copyright 2008 Milko Slavov
 * This file is part of GEH.
 * GEH is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * GEH is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * See <http://www.gnu.org/licenses/>.
*/

package com.geh.event;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * EventHandler class is used for generic event handling. An instance of this
 * class provides asynchronous event triggering mechanism.
 * 
 * @author mslavov
 * 
 * @param <E> The type of the events
 * @param <L> The type of the listeners
 */
public final class EventHandler<E, L>
{
   private ThreadPool   pool;
   private ArrayList<L> listeners = new ArrayList<L>();
   private String[]     actions;

   /**
    * Default constructor: creates an empty action array, maxEventsInQueue = 10
    * and maxWorkers = 10
    */
   public EventHandler()
   {
      this(new String[] {}, 10, 10);
   }

   /**
    * Creates an instance of EventHandler with actions array, which is used to
    * validate listeners
    * 
    * @param actions
    */
   public EventHandler(String[] actions)
   {
      this(actions, 10, 10);
   }

   /**
    * Creates an instance of EventHandler with empty actions array and given
    * maxEventsInQueue and maxWorkers values.
    * 
    * @param maxEventsInQueue
    * @param maxWorkers
    */
   public EventHandler(int maxEventsInQueue, int maxWorkers)
   {
      this(null, maxEventsInQueue, maxWorkers);
   }

   /**
    * Creates an instance of EventHandler where you can specify actions array,
    * maxEventsInQueue and maxWorkers
    * 
    * @param actions
    * @param maxEventsInQueue
    * @param maxWorkers
    */
   public EventHandler(String[] actions, int maxEventsInQueue, int maxWorkers)
   {
      pool = ThreadPool.newInstance(maxWorkers, maxEventsInQueue);
      if (actions != null)
         this.actions = actions.clone();
      else
         this.actions = new String[] {};
   }

   /**
    * This method is used fire generic event.
    * 
    * @param action The action
    * @param event The event of type <E>
    */
   public void fireEvent(String action, E event)
   {
      EventWorker<E> worker = new EventWorker<E>(action, event);
      if (!pool.isStopped())
         pool.enqueueTask(worker);
   }

   /**
    * Adds generic listener.
    * 
    * @param listener The listener of type <L>
    * @throws IllegalArgumentException If the given listener doesn't annotates
    *             at least one method with one of the given actions
    */
   public void addListener(L listener) throws IllegalArgumentException
   {
      if (checkListener(listener))
         listeners.add(listener);
      else
         throw new IllegalArgumentException("Invalid listener");
   }

   /**
    * Removes the specified listener
    * 
    * @param listener The listener of type <L>
    */
   public void removeListener(L listener)
   {
      listeners.remove(listener);
   }

   /**
    * As this mechanism is asynchronous and a Thread pool is used Calling this
    * method is necessary to stop the threads in the pool.
    * 
    */
   public void dispose()
   {
      pool.stopPool();
   }

   private boolean checkListener(L listener)
   {
      boolean isValid = false;
      Class clazz = listener.getClass();
      for (Method m : clazz.getMethods()) {
         EventListener annotationListener = m
                  .getAnnotation(EventListener.class);
         if (annotationListener != null) {
            if (actions.length > 0)
               for (int i = 0; i < actions.length; i++) {
                  if (annotationListener.value().equals(actions[i])) {
                     isValid = true;
                  }
               }
            else
               return true;
         }
      }
      return isValid;
   }

   private class EventWorker<T> implements Runnable
   {
      private String action;
      private T      event;

      public EventWorker(String action, T event)
      {
         this.action = action;
         this.event = event;
      }

      public void run()
      {
         for (int i = 0; i < listeners.size(); i++) {
            Class clazz = listeners.get(i).getClass();
            for (Method m : clazz.getMethods()) {
               EventListener listener = m.getAnnotation(EventListener.class);
               if (listener != null && this.action.equals(listener.value())) {
                  try {
                     m.invoke(listeners.get(i), this.event);
                  } catch (InvocationTargetException e) {
                     Throwable t = e.getTargetException();
                     for (Method errorMethod : clazz.getMethods()) {
                        ExceptionHandler handler = errorMethod
                                 .getAnnotation(ExceptionHandler.class);
                        if (handler != null) {
                           String[] exceptions = handler.value();
                           for (int j = 0; j < exceptions.length; j++) {
                              if (exceptions[j].equals(t.getClass().getName())) {
                                 try {
                                    errorMethod.invoke(listeners.get(i), t);
                                 } catch (Exception ex) {
                                    System.out
                                             .println("Exception handling failed");
                                    ex.printStackTrace();
                                 }
                              }
                           }
                        }
                     }
                  } catch (Exception e) {
                     System.out.println("Failed to fire event");
                  }
               }
            }
         }
      }
   }
}
